﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;

namespace MadLibrary.Math
{
    /// <summary>
    /// A static class containing mathematical utility methods and constants.
    /// </summary>
    public static class MathHelper
    {
        /// <summary>
        /// Represents the value of PI
        /// </summary>
        /// <remarks>
        /// Half a cycle in radians (180 degrees).
        /// </remarks>
        public const double PI = 3.1415926535897932384626433832795d;

        /// <summary>
        /// Represent a value that is PI multiplied by 2.
        /// </summary>
        /// <remarks>
        /// A full cycle in radians (360 degrees).
        /// </remarks>
        public const double TwoPI = 2 * PI;

        /// <summary>
        /// Represent a value that is PI divided by 2.
        /// </summary>
        /// <remarks>
        /// A quarter of a cycle in radians (90 degrees).
        /// </remarks>
        public const double HalfPI = PI / 2;

        /// <summary>
        /// Represent a value that is PI divided by 4.
        /// </summary>
        /// <remarks>
        /// An eighth of a cycle in radians (45 degrees).
        /// </remarks>
        public const double QuarterPI = PI / 4;

        /// <summary>
        /// The value of 2PI radians divided by 360 degrees.
        /// </summary>
        public const double RadDivDegree = TwoPI / 360.0;

        /// <summary>
        /// Returns the radian value of the given angle in degrees.
        /// </summary>
        /// <param name="degree">An angle in degree form.</param>
        /// <returns>The radian form of the given degree.</returns>
        public static double DegreesToRadians(this double degree)
        {
            return degree / RadDivDegree;
        }

        /// <summary>Returns the degree value of the given angle in radians.</summary>
        /// <param name="radian">An angle in radian form.</param>
        /// <returns>The degree form of the given radian.</returns>
        public static double RadiansToDegrees(this double radian)
        {
            return radian * RadDivDegree;
        }

        /// <summary>Determines whether the specified 'a' is even.</summary>
        /// <param name="a">A.</param>
        /// <returns>true if even, false if not.</returns>
        public static bool IsEven(this int a)
        {
            return (System.Math.IEEERemainder(a, 2) == 0);
        }

        /// <summary>Determines whether the specified 'a' is valid (Not NaN and Infinity).</summary>
        /// <param name="a">A.</param>
        /// <returns>true if valid, false if not.</returns>
        public static bool IsValid(this double a)
        {
            return !double.IsNaN(a) && !double.IsInfinity(a);
        }

        /// <summary>A Vector2D extension method that query if 'x' is valid.</summary>
        /// <param name="x">The x to act on.</param>
        /// <returns>true if valid, false if not.</returns>
        public static bool IsValid(this Vector2D x)
        {
            return IsValid(x.X) && IsValid(x.Y);
        }

        /// <summary>Checks if 'a' is within a specified range of values (inclusive).</summary>
        /// <param name="a">The Value to check.</param>
        /// <param name="min">The minimum Value.</param>
        /// <param name="max">The maximum Value.</param>
        /// <returns>True if the Value is within the range specified, false otherwise.</returns>
        public static bool IsInRange(this int a, int min, int max)
        {
            return (a >= min && a <= max);
        }

        /// <summary>Checks if 'a' is within a specified range of values (inclusive).</summary>
        /// <param name="a">The Value to check.</param>
        /// <param name="min">The minimum Value.</param>
        /// <param name="max">The maximum Value.</param>
        /// <returns>True if the Value is within the range specified, false otherwise.</returns>
        public static bool IsInRange(this double a, double min, double max)
        {
            return (a >= min && a <= max);
        }

        /// <summary>A double extension method that clamp the given a.</summary>
        /// <param name="a">A.</param>
        /// <param name="min">The minimum Value.</param>
        /// <param name="max">The maximum Value.</param>
        /// <returns>.</returns>
        public static double Clamp(this double a, double min, double max)
        {
            a = (a > max) ? max : a;
            a = (a < min) ? min : a;
            return a;
        }

        /// <summary>Calculates the distance / force between two vectors.</summary>
        /// <param name="a">The u to act on.</param>
        /// <param name="b">The v to act on.</param>
        /// <returns>.</returns>
        public static double DistanceBetween(this Vector2D a, Vector2D b)
        {
            return (a - b).Force;
        }

        /// <summary>
        /// Caculates the factorial of the given number.
        /// </summary>
        /// <param name="n">The number to calcuate</param>
        public static BigInteger Factorial(BigInteger n)
        {
            if (n <= 1)
                return 1;
            return n * Factorial(n - 1);
        }

        /// <summary>
        /// Checks if the given <see cref="System.Int64"/> is a prime number.
        /// </summary>
        /// <param name="this">The number to check</param>
        /// <returns>True if the number is prime, false otherwise.</returns>
        public static bool IsPrime(this long @this)
        {
            if (@this % 2 == 0 && @this != 2)
                return false;
            var root = System.Math.Sqrt(@this);
            for (long i = 3; i < root; i += 2)
            {
                if (@this % i == 0)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Checks if the given <see cref="System.UInt64"/> is a prime number.
        /// </summary>
        /// <param name="this">The number to check</param>
        /// <returns>True if the number is prime, false otherwise.</returns>
        public static bool IsPrime(this ulong @this)
        {
            if (@this % 2 == 0 && @this != 2)
                return false;
            var root = System.Math.Sqrt(@this);
            for (ulong i = 3; i < root; i += 2)
            {
                if (@this % i == 0)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Checks if the given <see cref="System.Numerics.BigInteger"/> is a prime number.
        /// </summary>
        /// <param name="this">The number to check</param>
        /// <returns>True if the number is prime, false otherwise.</returns>
        public static bool IsPrime(this BigInteger @this)
        {
            if (@this % 2 == 0 && @this != 2)
                return false;
            var root = @this.Sqrt();
            for (BigInteger i = 3; i < root; i += 2)
            {
                if (@this % i == 0)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Checks if the given <see cref="System.Double"/> is a prime number.
        /// </summary>
        /// <param name="this">The number to check</param>
        /// <returns>True if the number is prime, false otherwise.</returns>
        public static bool IsPrime(this double @this)
        {
            if (@this % 2 == 0 && @this != 2)
                return false;
            var root = System.Math.Sqrt(@this);
            for (double i = 3; i < root; i += 2)
            {
                if (@this % i == 0)
                    return false;
            }
            return true;
        }

        /// <summary>Extracts a root of the given BigInteger.</summary>
        /// <exception cref="ArithmeticException">Thrown when an error occurs in an arithmetic, casting or
        /// conversion operation.</exception>
        /// <param name="n">.</param>
        /// <returns>.</returns>
        public static BigInteger Sqrt(this BigInteger n)
        {
            if (n == 0) return 0;
            if (n > 0)
            {
                int bitLength = Convert.ToInt32(System.Math.Ceiling(BigInteger.Log(n, 2)));
                BigInteger root = BigInteger.One << (bitLength / 2);

                while (!IsSqrt(n, root))
                {
                    root += n / root;
                    root /= 2;
                }

                return root;
            }

            throw new ArithmeticException("NaN");
        }

        /// <summary>Query if 'n' is sqrt.</summary>
        /// <param name="n">The number to calcuate.</param>
        /// <param name="root">The root.</param>
        /// <returns>true if sqrt, false if not.</returns>
        public static Boolean IsSqrt(this BigInteger n, BigInteger root)
        {
            BigInteger lowerBound = root * root;
            BigInteger upperBound = (root + 1) * (root + 1);

            return (n >= lowerBound && n < upperBound);
        }
    }
}